package org.example.ams.controller.system;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.example.ams.util.SystemUtils;
import org.example.ams.vo.SignDataVo;
import org.example.common.api.CommonResult;
import org.example.common.constant.RedisConstant;
import org.example.common.utils.CommonAlgorithm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDate;
import java.util.List;

/**
 * 签到管理
 * @author cheval
 */
@Api(tags = "签到管理")
@RestController("/sign")
public class SignController {

    @Autowired
    private SystemUtils systemUtils;
    @Autowired
    private StringRedisTemplate redisTemplate;


    @ApiOperation("签到")
    @PostMapping("/sign")
    public CommonResult sign() {
        // 1. 获取当前登录用户id
        Integer userId = systemUtils.loginUser().getUserId();
        // 2. 拼接bitmap key: subfix:年:月:userId
        LocalDate now = LocalDate.now();
        int year = now.getYear();
        String month = (now.getMonth().getValue() + "").length() == 1
                ? "0" + now.getMonth().getValue() : now.getMonth().getValue() + "";
        String key = RedisConstant.SING_DATA_KEY_PREFIX + year + ":" + month + ":" + userId;
        // 3. 签到
        int day = now.getDayOfMonth();
        // 默认永不过期，redis需要做持久化
        redisTemplate.opsForValue().setBit(key, day - 1, true);
        return CommonResult.success(null, "签到成功");
    }

    @ApiOperation("获取知指定年月的签到数据. yearDate格式为 yyyy:MM")
    @GetMapping("/getSignData")
    public CommonResult getCurrentMonthSignData(@RequestParam(required = false) String yearDate) {
        /** 如果yearDate为空,则查询当前年月的签到数据 */
        String regExp = "[0-9]{4}:(0[1-9]|1[0-2])";
        String monthRegExp = "01|03|05|07|08|10|12";

        // 1. 获取当前登录用户id
        Integer userId = systemUtils.loginUser().getUserId();
        // 2. 拼接bitmap key: subfix:年:月:userId
        String key = null;
        String month = null;
        // 月天数
        int days = 30;
        int year = 0;
        LocalDate now = LocalDate.now();
        if (yearDate == null || "".equals(yearDate) || !yearDate.matches(regExp)) {
            year = now.getYear();
            month = (now.getMonth().getValue() + "").length() == 1
                    ? "0" + now.getMonth().getValue() : now.getMonth().getValue() + "";
            key = RedisConstant.SING_DATA_KEY_PREFIX + year + ":" + month + ":" + userId;
        } else {
            key = RedisConstant.SING_DATA_KEY_PREFIX + yearDate + ":" + userId;
            String[] arr = yearDate.split(":");
            month = arr[1];
            year = Integer.parseInt(arr[0]);
        }
        String specialMonth = "02";
        if (month.matches(monthRegExp)) {
            days = 31;
        } else if (specialMonth.equals(month) && CommonAlgorithm.isLeapYear(year)) {
            // 闰年2月29天
            days = 29;
        } else if (specialMonth.equals(month) && !CommonAlgorithm.isLeapYear(year)) {
            // 平年2月28天
            days = 28;
        }
        // 3. 获取签到数据 bitmap(获取 [0 ~ days - 1]之间的bit)
        List<Long> data = redisTemplate.opsForValue().
                bitField(key, BitFieldSubCommands.create()
                        .get(BitFieldSubCommands.BitFieldType.unsigned(days)).valueAt(0));
        // 4. 签到数据转化为二进制字符串
        String signData = "";
        if (data != null && data.size() > 0) {
            signData = Long.toBinaryString(data.get(0));
        }
        StringBuilder preBits = new StringBuilder();
        for (int i = 0; i < days - signData.length(); i++) {
            preBits.append("0");
        }
        signData = preBits + signData;
        // 5. 获取连续签到天数
        int count = 0;
        int currentDay = now.getDayOfMonth();
        // 获取取[0 - currentDay]的bit位的值，然后就是leecode算法了
        /**
         * 题目： 给定一个整数，求统计整数的二进制中有几个1
         * 一般思路： 先将整数转换成二进制字符串,然后遍历求解
         * 技巧思路： 位运算， 将这个数与1相与，判断结果是否是1，如果是，计数器加一。
         * 然后将这个数右移一位后继续以上操作，直到右移后的数为0.
         */
        year = now.getYear();
        month = (now.getMonth().getValue() + "").length() == 1
                ? "0" + now.getMonth().getValue() : now.getMonth().getValue() + "";
        key = RedisConstant.SING_DATA_KEY_PREFIX + year + ":" + month + ":" + userId;
        List<Long> nums = redisTemplate.opsForValue()
                .bitField(key,BitFieldSubCommands.create()
                        .get(BitFieldSubCommands.BitFieldType.unsigned(currentDay)).valueAt(0));
        if (nums != null && nums.size() > 0) {
            Long num = nums.get(0);
            while (num != 0) {
                if ((num & 1) == 1) {
                    count++;
                    num = num >> 1;
                } else {
                    break;
                }
            }
        }
        // 6. 统计签到次数和未签到次数
        int signCount = 0;
        if (data != null && data.size() > 0) {
            signCount = Long.bitCount(data.get(0));
        }
        // 7. 组装数据返回
        SignDataVo signDataVo = new SignDataVo();
        signDataVo.setDays(days);
        signDataVo.setSignData(signData);
        signDataVo.setCount(count);
        signDataVo.setSignCount(signCount);
        return CommonResult.success(signDataVo,null);
    }
}
